/*
 * Copyright 2011 Romain Gilles
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jvdb;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.util.Properties;

import static org.jvdb.ConfigUtil.LOGGER_BASE_NAME;

/**
 * TODO_LIST:
 * - Handle configuration properties:
 * - Env / VM Args / Properties Info overriding
 * - Handle underlying driver instantiation
 * - Handle log file vs console
 * - Handle object context (factory ...)
 * - TODO: Create a default no driver implementation for underlying driver
 * - ...
 */
public class Driver3 implements java.sql.Driver {

    protected final Logger logger = LoggerFactory.getLogger(LOGGER_BASE_NAME + ".driver");

    static final String BASE_SCHEME_URL = "jdbc:jvdb";

    private static final Properties EMPTY_PROPERTY = new Properties();

    private static volatile java.sql.Driver underlyingDriver = null;

    static {
        try {
            DriverManager.registerDriver(new Driver3());
        } catch (SQLException s) {
            // this exception should never be thrown, JDBC just defines it
            // for completeness
            throw (RuntimeException) new RuntimeException
                    ("could not register jvdb driver!").initCause(s);
        }
        getUnderlyingDriverInstance(null, false);
    }

    private static final ConnectionFactory connectionFactory = AbstractConnectionFactory.newInstance();

    /**
     * {@inheritDoc}
     */
    public Connection connect(String url, Properties info) throws SQLException {
        logger.debug("Create a connection for url: {}, info: {}", url, info);
        if (acceptsURL(url))
            return connectionFactory.create(getUnderlyingDriverInstance(info), getDelegateDriverURL(url), info);
        return null;
    }


    private static java.sql.Driver getNotFailUnderlyingDriverInstance() {
        return getUnderlyingDriverInstance(EMPTY_PROPERTY, false);
    }

    static java.sql.Driver getUnderlyingDriverInstance(Properties info) {
        return getUnderlyingDriverInstance(info, true);
    }

    /**
     * Returns the wrapped driver instance.
     * Single-check idiom - it can cause repeated initialization but according to the {@link java.sql.Driver Driver}
     * documentation this class is a lightweight class and it is may support multy instance.
     *
     * @param info     the property configuration where driver class name can be found. It can be {@code null}.
     * @param failFast if true then throw un exception if the
     * @return the underlying driver instance on which the real action will delegated.
     * @throws JVBDConfigurationException if an exception occurred during instantiation of the wrapped driver. Or if
     */
    static java.sql.Driver getUnderlyingDriverInstance(Properties info, boolean failFast) {
        java.sql.Driver result = underlyingDriver;
        if (result == null) {
            if (info == null)
                info = EMPTY_PROPERTY;

            String wrappedDriver = ConfigUtil.getWrappedDriver(info);
            if (wrappedDriver != null) {
                try {
                    underlyingDriver = result = (java.sql.Driver) Class.forName(wrappedDriver).newInstance();
                } catch (Exception e) {
                    throw new JVBDConfigurationException("could not register Wrapped drivers: " + wrappedDriver, e);
                }
            } else if (failFast) {
                throw new JVBDConfigurationException("could not work without wrapped driver class name.");
            }
        }
        return result;
    }

//    private static java.sql.Driver createWrappedDriver(Properties info, boolean failFast) {
//        if (info == null)
//                info = EMPTY_PROPERTY;
//
//            String wrappedDriver = ConfigUtil.getValue(WRAPPED_DRIVER, info);
//            if (wrappedDriver != null) {
//                try {
//                    return (java.sql.Driver) Class.forName(wrappedDriver).newInstance();
//                } catch (Exception e) {
//                    throw new JVBDConfigurationException("could not register Wrapped drivers: " + wrappedDriver, e);
//                }
//            }
//            else if (failFast) {
//                throw new JVBDConfigurationException("could not work without wrapped driver class name.");
//            }
//        return null;
//    }

    public static class JVBDConfigurationException extends RuntimeException {
        public JVBDConfigurationException(String message) {
            super(message);
        }

        public JVBDConfigurationException(String message, Throwable cause) {
            super(message, cause);
        }
    }


    /**
     * {@inheritDoc}
     */
    public boolean acceptsURL(String url) throws SQLException {
        return url.startsWith(BASE_SCHEME_URL) && acceptsURLDelegate(url);
    }

    /**
     * by default returns true until the underlying driver is loaded.
     * If the driver is loaded it also add a check against the driver.
     *
     * @param url driver url than contains t
     */
    private boolean acceptsURLDelegate(String url) throws SQLException {
        java.sql.Driver driver = getNotFailUnderlyingDriverInstance();
        if (driver != null) {
            String underlyingURL = getDelegateDriverURL(url);
            return driver.acceptsURL(underlyingURL);
        }
        return true;
    }

    static String getDelegateDriverURL(String url) {
        return url.substring(BASE_SCHEME_URL.length());
    }

    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
        //TODO handle jvdb specific properties as driver class...
        java.sql.Driver driver = getUnderlyingDriverInstance(info, false);
        if (driver == null) {
            return null;
        }
        return driver.getPropertyInfo(url, info);
    }

    public int getMajorVersion() {
        java.sql.Driver driver = getNotFailUnderlyingDriverInstance();
        if (driver == null) {
            return 3;
        }
        return driver.getMajorVersion();
    }

    public int getMinorVersion() {
        java.sql.Driver driver = getNotFailUnderlyingDriverInstance();
        if (driver == null) {
            return 0;
        }
        return driver.getMinorVersion();
    }

    public boolean jdbcCompliant() {
        java.sql.Driver driver = getNotFailUnderlyingDriverInstance();
        if (driver == null) {
            return false;
        }
        return driver.jdbcCompliant();
    }
}
